/**********************************************************************************************************************
 * @file    ubuff.c
 * @author  Queclink Hubert.Lee
 * @date    2016-12-15
 * @brief   Universal Buffer Operation Interface.
 *
 * Copyright (C) 2016 Queclink Wireless Solutions (ShenZhen) Co., Ltd. All Rights Reserved.
 *
 * @attention
 *
 *********************************************************************************************************************/

#ifndef HDL_FOR_FreeRTOS

/* includes ---------------------------------------------------------------------------------------------------------*/

#include <public/ubuff.h>
#include <public/errno.h>
#include <public/list.h>
#include <stdbool.h>
#include <stdlib.h>
#include "public/weak.h"


struct ubuff_t {
	int                     id;				/* buffer id */
	void                    *buff;			/* buffer base address */
	size_t                  size;			/* buffer size */
	volatile size_t		wr;				/* write offset pointer, point to the next byte should be stored */
	volatile size_t		rd;				/* read offset pointer, point to the next byte will be read */
	volatile bool		full_or_empty;	/* true: buffer is full when wr equal to rd, false: buffer is empty when wr equal to rd */
	struct list_head	list;
};

static struct list_head ubuffs = LIST_HEAD_INIT(ubuffs);

int ubuff_register(const void *buf, size_t size)
{
	struct ubuff_t *ubuff = (struct ubuff_t *)lib_malloc(sizeof(struct ubuff_t));
	struct list_head *pos;
	int id = 0;

	if (NULL == ubuff)
		return -ENOMEM;

	/* allocate id */
	list_for_each(pos, &ubuffs)
		id++;
	for (;;) {
		bool exist = false;
		list_for_each(pos, &ubuffs) {
			struct ubuff_t *_ubuff = list_entry(pos, struct ubuff_t, list);
			if (id == _ubuff->id) {
				exist = true;
				id++;
			}
		}
		if (false == exist)
			break;
	}

	ubuff->id = id;
	ubuff->buff = (void *)buf;
	ubuff->size = size;
	ubuff->wr = ubuff->rd = 0;
	ubuff->full_or_empty = false;

	list_add_tail(&ubuff->list, &ubuffs);

	return id;
}

static struct ubuff_t *ubuff_search(int id)
{
	struct list_head *pos;

	list_for_each(pos, &ubuffs) {
		struct ubuff_t *ubuff = list_entry(pos, struct ubuff_t, list);
		if (id == ubuff->id)
			return ubuff;
	}

	return NULL;
}

int ubuff_unregister(int id)
{
	struct ubuff_t *ubuff = ubuff_search(id);

	if (NULL != ubuff) {
		list_del(&ubuff->list);
		lib_free(ubuff);
	}

	return 0;
}

int ubuff_clear(int id)
{
	struct ubuff_t *ubuff = ubuff_search(id);

	if (NULL != ubuff) {
		ubuff->wr = ubuff->rd = 0;
		ubuff->full_or_empty = false;
	}

	return 0;
}

size_t ubuff_data_size(int id)
{
	struct ubuff_t *ubuff = ubuff_search(id);

	if (NULL != ubuff) {
		size_t wr, rd;
		wr = ubuff->wr;
		rd = ubuff->rd;
		if (rd < wr)
			return wr - rd;
		else if (rd > wr)
			return ubuff->size - rd + wr;
		else if ((rd == wr) && (ubuff->full_or_empty))
			return ubuff->size;
		else
			return 0;
	}

	return 0;
}

size_t ubuff_empty_size(int id)
{
	struct ubuff_t *ubuff = ubuff_search(id);

	if (NULL != ubuff) {
		size_t wr, rd;
		wr = ubuff->wr;
		rd = ubuff->rd;
		if (wr < rd)
			return rd - wr;
		else if (wr > rd)
			return ubuff->size - wr + rd;
		else if ((rd == wr) && (!ubuff->full_or_empty))
			return ubuff->size;
		else
			return 0;
	}

	return 0;
}

ssize_t ubuff_write(int id, const void *buf, size_t count)
{
	struct ubuff_t *ubuff = ubuff_search(id);
	ssize_t cnt = 0;

    if (!count)
        return 0;

	if (NULL != ubuff) {
		size_t rd = ubuff->rd;
		while (!((rd == ubuff->wr) && (ubuff->full_or_empty))) {
			*((char *)ubuff->buff + ubuff->wr) = *(char *)buf;
			ubuff->wr++; buf = (char *)buf+1; cnt++;
			if (ubuff->size <= ubuff->wr)
				ubuff->wr = 0;
			if (rd == ubuff->wr) {
				ubuff->full_or_empty = true;
				break;
			} else if (count <= cnt) {
				break;
			}
		}
		return cnt;
	}

	return 0;
}

ssize_t ubuff_read(int id, void *buf, size_t count)
{
	struct ubuff_t *ubuff = ubuff_search(id);
	ssize_t cnt = 0;

    if (!count)
        return 0;

	if (NULL != ubuff) {
		size_t wr = ubuff->wr;
		while (!((wr == ubuff->rd) && (!ubuff->full_or_empty))) {
			*(char *)buf = *((char *)ubuff->buff + ubuff->rd);
			ubuff->rd++; buf = (char *)buf+1; cnt++;
			if (ubuff->size <= ubuff->rd)
				ubuff->rd = 0;
			if (wr == ubuff->rd) {
				ubuff->full_or_empty = false;
				break;
			} else if (count <= cnt) {
				break;
			}
		}
		return cnt;
	}

	return 0;
}

ssize_t ubuff_read_dummy(int id, void *buf, size_t count)
{
	struct ubuff_t *ubuff = ubuff_search(id);
	ssize_t cnt = 0;

    if (!count)
        return 0;

	if (NULL != ubuff) {
		size_t wr = ubuff->wr;
        size_t rd = ubuff->rd;
		while (!((wr == rd) && (!ubuff->full_or_empty))) {
			*(char *)buf = *((char *)ubuff->buff + rd);
			rd++; buf = (char *)buf+1; cnt++;
			if (ubuff->size <= rd)
				rd = 0;
			if (wr == rd)
				break;
	        else if (count <= cnt)
				break;
		}
		return cnt;
	}

	return 0;
}

#endif /* HDL_FOR_FreeRTOS */